/*******************************************************************************
 * @file fdt_shmmgr.c
 * @brief  inter process Fast Data Transfer
 *
 *
 * Author: Kevinx
 *
 * Copyright (c) 2015 Kevinx. All Rights Reserved.
 *
 * CONFIDENTIALITY AND LIMITED USE
 *
 * This software, including any software of third parties embodied herein,
 * contains information and concepts which are confidential to Kevinx
 * and such third parties. This software is licensed for use
 * solely in accordance with the terms and conditions of the applicable
 * license agreement with Kevinx or its authorized distributor.
 ************************************************************************************/
#include "fdt_shmmgr.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include "mbuf.h"
#include "fdt_channel.h"

/*
 * open_share_memory: open shared memory, then return the start address
 *
 */
static void* open_share_memory(const char* id, const char* fmt, uint32_t size)
{
    int shmid;
    key_t key;
    int fd;
    char pathname[128] = {0};
    void* address = (void*)-1;

    sprintf(pathname, fmt, id);

    fd = open(pathname, O_CREAT | O_RDWR, 00666);
    if (unlikely(fd < 0)) {
        printf("%s(%d):open %s fail: errno=[%d:%s]\n", 
                __func__, __LINE__, 
                pathname, errno, strerror(errno));
        return  (void*)-1;
    }

    key = ftok(pathname, 0);
    if (key == -1) {
        goto on_return;
    }

    shmid = shmget(key, size, IPC_CREAT);
    if (shmid == -1) {
        goto on_return;
    }

    address = shmat(shmid, NULL, 0);
    if (address == (void*)-1) {
        printf("syscall shmat fail: errno=[%d: %s]\n", 
                errno, strerror(errno));        
        goto on_return;
    }

on_return:
    close(fd);
    return address;    
}

/*
 * close shared memory specificed by address "shmaddr"
 *
 */
static int close_share_memory(void* shmaddr)
{
    int rc;
    rc =  shmdt(shmaddr);
    if (rc == -1)
        printf("syscall shmdt fail: errno=[%d: %s]\n", 
                errno, strerror(errno)); 

    return rc;
}

/*
 * fdt_shmmgr_create: create share memory, init the free mbuf list *
 *
 */
int fdt_shmmgr_create(struct fdt_shmmgr* shmmgr, const char* id, uint32_t size)
{
    struct mbuf* m;
    struct shm_area_struct* shma;
    address_t start;
    int i;

    void* ptr = open_share_memory(id, "/tmp/%s.shmarea", size);
    if (ptr == (void*)-1) {
        return -1;
    }

    int nbits = size >> M_ORDER;
	int nbytes = sizeof(struct shm_area_struct) +
				(nbits + BITS_PER_BYTE - 1) / BITS_PER_BYTE;

    shma = (struct shm_area_struct*)malloc(nbytes);
    shma->start_addr = ptr_to_address(ptr);
    shma->end_addr = shma->start_addr + size;
    shma->size = size;
    INIT_LIST_HEAD(&shma->list);
    memset(shma->bits, 0x0, ((nbits + BITS_PER_BYTE - 1) / BITS_PER_BYTE));
    
    shmmgr->mbuf_area = shma;

#ifdef FDT_DEBUG
    printf("LWP %d: Shmmgr SHM Area: 0x%08x - 0x%08x (Size: %lu M)\n", gettid(), shma->start_addr, shma->end_addr, (shma->size >> 20));
#endif

    INIT_LIST_HEAD(&shmmgr->mbuf_list);
    shmmgr->m_count = 0;
    
    start = shma->start_addr;
    for (i = 0; start < shma->end_addr; start += M_SIZE) {
        //m = (struct mbuf*)start;
        m = dtom(start);        
        m_init(m);
        m_hdr(m)->id = i++;

        list_add_tail(&m->m_list, &shmmgr->mbuf_list);
        shmmgr->m_count ++;
    }

    return 0;
}

/*
 * fdt_shmmgr_destory: destroy shmmgr object, delete shared memory *
 *
 */
int fdt_shmmgr_destory(struct fdt_shmmgr* shmmgr)
{
    close_share_memory(address_to_ptr(shmmgr->mbuf_area->start_addr));
    free(shmmgr->mbuf_area);

    INIT_LIST_HEAD(&shmmgr->mbuf_list);
    shmmgr->m_count = 0;

    return 0;
}

static int test_and_set_bit(int nr, ulong_t *addr)
{
	ulong_t mask = BITOP_MASK(nr);
	ulong_t *p = ((ulong_t *)addr) + BITOP_WORD(nr);
	ulong_t old;

	old = *p;
	*p = old | mask;

	return (old & mask) != 0;
}

static int test_and_clear_bit(int nr, ulong_t *addr)
{
	ulong_t mask = BITOP_MASK(nr);
	ulong_t *p = ((ulong_t *)addr) + BITOP_WORD(nr);
	ulong_t old;

	old = *p;
	*p = old & ~mask;

	return (old & mask) != 0;
}

/*
 * fdt_shmmgr_alloc: allocate a buffer from shmmgr's pool
 *
 */
static struct mbuf* __fdt_shmmgr_alloc(struct fdt_shmmgr* shmmgr)
{
    struct list_head *entry = NULL;
    struct mbuf* m = NULL;
    int bit;
    struct shm_area_struct* shma = shmmgr->mbuf_area;

    if (!list_empty(&shmmgr->mbuf_list)) {
        entry = shmmgr->mbuf_list.next;
        list_del(entry);
        shmmgr->m_count --;
 
        m = list_entry(entry, struct mbuf, m_list);
        
        bit = (ptr_to_address(m) - shma->start_addr) >> M_ORDER;
 		if (unlikely(test_and_set_bit(bit, shma->bits))) {
			printf("BUG(%s): shm area bitmap is not correct!", __func__);
		}
    }

    return m? (mtod(m, void*)) :NULL;    
}

void* fdt_shmmgr_alloc(struct fdt_shmmgr* shmmgr)
{
	struct mbuf* m = NULL;

	m = __fdt_shmmgr_alloc(shmmgr);

	return m? (mtod(m, void*)) :NULL;
}

void* fdt_shmmgr_alloc_with_backoff(struct fdt_shmmgr* shmmgr)
{
	struct mbuf* m = NULL;

	m = __fdt_shmmgr_alloc(shmmgr);

	return m? (mtodbuf(m, void*)) :NULL;
}

/*
 * fdt_shmmgr_free: free a buffer to shmmgr's pool
 *
 */
void fdt_shmmgr_free(struct fdt_shmmgr* shmmgr, void* p)
{
    struct mbuf* m = dtom(p);
    struct shm_area_struct* shma = shmmgr->mbuf_area;
    address_t addr = ptr_to_address(p);
    int bit;
    struct fdt_buffer buffer;

    if (!(m_hdr(m)->flags & MF_GARBAGE) && m_hdr(m)->in_ch) {

        if (m_hdr(m)->in_ch->flags & FCH_NO_NEED_GR) {
            /* no need to return this buffer, just re-init it*/
            m_init(m);
        } else {

            buffer.packet_ptr.ptr = p;
            buffer.len = 0;
            fdt_PTRch_greturn(m_hdr(m)->in_ch, &buffer, sizeof(struct fdt_buffer));            
        }
    }

    if (shma->end_addr > addr && shma->start_addr <= addr) {
        bit = (addr - shma->start_addr) >> M_ORDER;
        if (unlikely(!test_and_clear_bit(bit, shma->bits))) {
            printf("Buffer at %lx(idx: %d) is already free.\n", addr, bit);
        } /*else 
            printf("Buffer sucess\n");*/

        m_init(m);
        list_add(&m->m_list, &shmmgr->mbuf_list);
        shmmgr->m_count ++;
    }
}

/*
 * fdt_shmmgr_mbuf_number:
 *  get free buffer number in shmmgr's pool
 *
 * @return: number of buffer
 */
int fdt_shmmgr_mbuf_number(struct fdt_shmmgr* shmmgr)
{
    return shmmgr->m_count;
}

/*
 * fdt_shmmgr_attach_shma:
 *  create a shm_area_struct object and attaches the shared memory segment identified  by id
 *
 */
struct shm_area_struct* fdt_shmmgr_attach_shma(const char* id, uint32_t size)
{
    struct shm_area_struct* shma = NULL;
    void* ptr = open_share_memory(id, "/tmp/%s.shmarea", size);
    if (ptr == (void*)-1) {
        return NULL;
    }
    
    shma = (struct shm_area_struct*)malloc(sizeof(struct shm_area_struct));
    shma->start_addr = ptr_to_address(ptr);
    shma->end_addr = shma->start_addr + size;
    shma->size = size;

#ifdef FDT_DEBUG
    printf("LWP %d: Attach Reference SHM Area: 0x%08x - 0x%08x (Size: %lu M)\n", (int)gettid(), shma->start_addr, shma->end_addr, (shma->size >> 20));
#endif

    return shma;    
}

/*
 * fdt_shmmgr_del_shma:
 *  delete a shm_area_struct object and deattaches the shared memory segment in it
 *
 */
int fdt_shmmgr_del_shma(struct shm_area_struct* shma)
{
    int rc = close_share_memory(address_to_ptr(shma->start_addr));
    if (rc == 0) {
        free(shma);
        return 0;
    }

    return -1;
}

